﻿namespace Trader.Indicators.TechnicalAnalysis
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using BO.Interface;
    using Interface;

    ///<seealso cref="http://www.chartpatterns.com/headandshoulders.htm"/>
    public class HeadAndShouldersSignal : ISignal
    {
        #region Private Member's

        ///<summary>
        ///Number of days to check before the buy date</summary>
        private readonly int days;

        public int TestDays
        {
            get { return days; }
        }

        public Guid ID
        {
            get { return new Guid("D6FD039A-D840-4688-99E0-F688700A8C7C"); }
        }

        public string Name
        {
            get { return string.Format("HeadAndShoulders ({0})", days); }
        }

        public Uri URL
        {
            get { return new Uri("http://www.chartpatterns.com/headandshoulders.htm"); }
        }

        #endregion

        #region Cons'

        ///<summary>
        ///HeadAndShouldersSignal constructor: calculates the days and deviation</summary>
        public HeadAndShouldersSignal(int days)
        {
            this.days = days;
        }

        #endregion

        #region ISignal Members

        ///<summary>
        ///Generate a recomendation for the stock at the spesific time
        ///</summary>
        ///<param name="stock"> The stock to buy</param>
        ///<param name="buyTime"> The date for buying</param>
        ///<returns>Yes or No</returns>
        public Recommendation GenerateRecommendation(IStock stock, DateTime buyTime)
        {
            if (!stock.Quotes.ContainsKey(buyTime)) //Check if buy point has quote
            {
                return Recommendation.NONE;
            }

            IList<decimal> highPrices = GetHighPrices(stock, buyTime);
            IList<decimal> lowPrices = GetLowPrices(stock, buyTime);

            if (!highPrices.Any()) //No quotes during period
            {
                return Recommendation.NONE;
            }
            if (!lowPrices.Any()) //No quotes during period
            {
                return Recommendation.NONE;
            }

            int numOfValus = highPrices.Count;

            int max1Index = -1;
            int max2Index = -1;
            int min1Index = -1;
            int min2Index = -1;
            int min3Index = -1;
            int i = 0;
            decimal max1Value = 0;
            decimal max2Value = 0;
            decimal min1Value = 10000;
            decimal min2Value = 10000;
            decimal min3Value = 10000;

            foreach (decimal num in highPrices)
            {
                if (num > max1Value)
                {
                    max2Value = max1Value;
                    max2Index = max1Index;

                    max1Value = num;
                    max1Index = i;
                }
                else if (num > max2Value && num != max1Value)
                {
                    max2Value = num;
                    max2Index = i;
                }

                i++;
            }

            i = 0;
            foreach (decimal num in lowPrices)
            {
                if (num < min1Value)
                {
                    min3Value = min2Value;
                    min3Index = min2Index;

                    min2Value = min1Value;
                    min2Index = min1Index;

                    min1Value = num;
                    min1Index = i;
                }
                else if (num < min2Value && num != min1Value)
                {
                    min3Value = min2Value;
                    min3Index = min2Index;

                    min2Value = num;
                    min2Index = i;
                }
                else if (num < min3Value && num != min2Value)
                {
                    min3Value = num;
                    min3Index = i;
                }
                i++;
            }

            if (numOfValus < 2)
            {
                return Recommendation.NONE;
            }

            IList<decimal> volumes = GetVolumes(stock, buyTime);

            // Check for reverse pattern from downtrend to "bull"

            // Check if the lows and highs are in the correct formation 
            if ((min3Index < max1Index && max1Index < min1Index
                 && min1Index < max2Index && max2Index < min2Index) ||
                (min2Index < max1Index && max1Index < min1Index
                 && min1Index < max2Index && max2Index < min3Index))
            {
                // Uptrend
                if (stock.Quotes[buyTime].High > highPrices[numOfValus - 1] &&
                    stock.Quotes[buyTime].Volume > volumes[numOfValus - 1]*1.2M)
                {
                    return Recommendation.BUY;
                }
            }

            return Recommendation.NONE;
        }

        #endregion

        ///<summary>
        ///Get Period Closings
        ///</summary>
        ///<param name="stock"> The stock to buy</param>
        ///<param name="buyTime"> The date for buying</param>
        ///<returns></returns>
        private IEnumerable<decimal> GetPeriodClosings(IStock stock, DateTime buyTime)
        {
            var periodClosings = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    periodClosings.Add(quote.Closing);
                }
            }
            return periodClosings;
        }

        ///<summary>
        ///Get all high prices of the stock from the time before the buy date
        ///</summary>
        ///<param name="stock"> The stock</param>
        ///<param name="buyTime"> The buy date</param>
        ///<returns> List of high prices</returns>
        private IList<decimal> GetHighPrices(IStock stock, DateTime buyTime)
        {
            var highPrices = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    highPrices.Add(quote.High);
                }
            }
            return highPrices;
        }

        ///<summary>
        ///Get all low prices of the stock from the time before the buy date
        ///</summary>
        ///<param name="stock"> The stock</param>
        ///<param name="buyTime"> The buy date</param>
        ///<returns> List of low prices</returns>
        private IList<decimal> GetLowPrices(IStock stock, DateTime buyTime)
        {
            var lowPrices = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    lowPrices.Add(quote.Low);
                }
            }
            return lowPrices;
        }

        ///<summary>
        ///Get all stock trade volumes from the time before the buy date
        ///</summary>
        ///<param name="stock"> The stock</param>
        ///<param name="buyTime"> The buy date</param>
        ///<returns> List of trade volumes</returns>
        private IList<decimal> GetVolumes(IStock stock, DateTime buyTime)
        {
            var volume = new List<decimal>();
            for (DateTime dateIterator = buyTime - TimeSpan.FromDays(days + 1);
                 dateIterator < buyTime;
                 dateIterator += TimeSpan.FromDays(1))
            {
                IQuote quote;
                if (stock.Quotes.TryGetValue(dateIterator, out quote))
                {
                    volume.Add(quote.Volume);
                }
            }
            return volume;
        }

        public override string ToString()
        {
            return string.Format("HeadAndShoulders({0})", days);
        }
    }
}